/*
 * This file is part of ddesktop: remote desktop system for iPad 
 *
 * Copyright (C) 2010 Kazuhiro Satoh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. if not, see <http://www.gnu.org/licenses/>.
 */


#include "stdafx.h"
#include "DDServerPort.h"

#pragma unmanaged

#include <windows.h>
#include <errno.h>
#include <string.h>

#include "DDServerPort.h"

namespace DDesktopServer{

	DDServerPort::DDServerPort(void)
	{
		m_send_data_port = 0;
		m_receive_command_port = 0;
		m_receive_command_thread_handle = 0;
		m_send_data_socket = INVALID_SOCKET;
		m_send_data_socket_is_opened = false;
		m_is_connected = false;
		m_client_addr_str[0] = '\0';
	}

	DDServerPort::~DDServerPort()
	{
		Close();
		::WSACleanup();
	}


	bool DDServerPort::Init(int command_port, IDDServerPortCommandDelegator* delegator)
	{
		WSADATA wsa_data;
		::WSAStartup(MAKEWORD(2,0), &wsa_data);

		m_terminate_receive_thread = false;
		m_receive_command_port = command_port;
		m_receive_command_thread_handle = ::CreateThread(NULL, 0, ReceiveCommandThreadProc, (void*)this, 0, NULL);
		if(m_receive_command_thread_handle == NULL){
			return false;
		}

		m_command_delegator = delegator;

		delegator->DDServerPortChangeState();

		return true;
	}


	bool DDServerPort::Close(void)
	{
		if(CloseSendDataSocket() == false){
			return false;
		}

		if(m_receive_command_thread_handle != 0){
			m_terminate_receive_thread = true;

			if(::WaitForSingleObject(m_receive_command_thread_handle, INFINITE) == WAIT_FAILED){
				return false;
			}
			if(::CloseHandle(m_receive_command_thread_handle) == 0){
				return false;
			}
			m_receive_command_thread_handle = 0;
		}

		return true;
	}




	// return false if connection closed or socket error
	static bool RecvByte(SOCKET sock, char* buf, int len)
	{
		int result;
		int recv_len = 0;

		while(recv_len < len){
			result = ::recv(sock, buf, len, 0);
			if(result == 0 || result == SOCKET_ERROR){
				return false;
			}
			recv_len += result;
		}

		return true;
	}

	static bool SendByte(SOCKET sock, const char* buf, int len)
	{
		int remain;
		int send_len;

		remain = len;
		while(remain > 0){
			send_len = ::send(sock, buf, remain, 0);
			if(send_len <= 0){
				return false;
			}
			remain -= send_len;
			buf += send_len;
		}

		return true;
	}


	DWORD WINAPI DDServerPort::ReceiveCommandThreadProc(LPVOID _self){
		DDServerPort* self = (DDServerPort*)_self;
		SOCKET sock0;
		struct sockaddr_in addr;
		int len;
		SOCKET sock;
		fd_set fds, readfds;
		struct timeval tv;

		sock0 = ::socket(AF_INET, SOCK_STREAM, 0);
		addr.sin_family = AF_INET;
		addr.sin_port = ::htons(self->m_receive_command_port);
		addr.sin_addr.S_un.S_addr = INADDR_ANY;
		::bind(sock0, (struct sockaddr *)&addr, sizeof(addr));
		::listen(sock0, 0);

		FD_ZERO(&readfds);
		FD_SET(sock0, &readfds);
		tv.tv_sec = 10;
		tv.tv_usec = 0;

		while(!self->m_terminate_receive_thread){
			self->m_is_connected = false;

			memcpy(&fds, &readfds, sizeof(fd_set));
			if (select(0, &fds, NULL, NULL, &tv) != 0) {
				len = sizeof(self->m_client_addr);
				sock = accept(sock0, (struct sockaddr *)&self->m_client_addr, &len);
				strcpy(self->m_client_addr_str, inet_ntoa(self->m_client_addr.sin_addr));
				self->m_is_connected = true;
				self->m_command_delegator->DDServerPortChangeState();
			}
			else{
				self->m_is_connected = false;
			}

			while(!self->m_terminate_receive_thread && self->m_is_connected){
				char command = '\0';

				{ // wait for START_CODE
					char ch = 'x';
					while(!self->m_terminate_receive_thread && ch != DD_START_CODE && self->m_is_connected){
						self->m_is_connected = RecvByte(sock, &ch, sizeof(ch));
					}
					if(!self->m_is_connected){
						break;
					}
				}

				self->m_is_connected = RecvByte(sock, &command, sizeof(command));
				switch(command){
				case DD_REQUEST_IMAGE_HEADER:
					{
						const unsigned char* header_buf;
						DWORD header_len, h_header_len;
						DWORD recv_buf[3];
						self->m_is_connected = RecvByte(sock, (char*)recv_buf, sizeof(recv_buf));
						self->m_command_delegator->DDServerPortCommandSendImageHeader(
							htonl(recv_buf[0]),  // image width
							htonl(recv_buf[1]),  // image height
							htonl(recv_buf[2]),  // image scale
							&header_buf,
							(int*)&header_len
						);
						h_header_len = htonl(header_len);
						::send(sock, (const char*)&h_header_len, sizeof(h_header_len), 0);
						::send(sock, (const char*)header_buf, header_len, 0);
					}
					break;
				case DD_REQUEST_IMAGE_DATA:
					{
						DWORD recv_buf[4];
						char* buf;
						DWORD len, h_len;
						self->m_is_connected = RecvByte(sock, (char*)recv_buf, sizeof(recv_buf));
						self->m_command_delegator->DDServerPortCommandDownloadFrame(
							htonl(recv_buf[0]),  // x
							htonl(recv_buf[1]),  // y
							htonl(recv_buf[2]),  // width
							htonl(recv_buf[3]),  // height
							&buf,
							&len
						);
						h_len = htonl(len);
						self->m_client_addr.sin_port = ntohs(12345);
						self->m_is_connected = SendByte(sock, (char*)&h_len, sizeof(h_len));
						self->m_is_connected = SendByte(sock, buf, len);
					}
					break;
				case DD_CHANGE_DOWNLOAD_FRAME_RECT:
					{
						DWORD recv_buf[5];
						self->m_is_connected = RecvByte(sock, (char*)recv_buf, sizeof(recv_buf));
						self->m_command_delegator->DDServerPortCommandSetDownloadFrame(
							htonl(recv_buf[0]),  // x
							htonl(recv_buf[1]),  // y
							htonl(recv_buf[2]),  // width
							htonl(recv_buf[3]),  // height
							htonl(recv_buf[4])   // zoomScale
						);
					}
					break;
				case DD_CHANGE_IMAGE_QUALITY:
					{
						DWORD recv_buf[1];
						self->m_is_connected = RecvByte(sock, (char*)recv_buf, sizeof(recv_buf));
						self->m_command_delegator->DDServerPortCommandChangeImageQuality(htonl(recv_buf[0]));
					}
					break;
				case DD_CHANGE_IMAGE_RESOLUTION:
					{
						DWORD recv_buf[1];
						self->m_is_connected = RecvByte(sock, (char*)recv_buf, sizeof(recv_buf));
						self->m_command_delegator->DDServerPortCommandChangeImageResolution(htonl(recv_buf[0]));
					}
					break;
				case DD_SEND_UI_EVENT:
					{
						DWORD buf[4];
						enum DDUIEventType type;
						enum DDUIEventOwner owner;
						DWORD x, y;
						self->m_is_connected = RecvByte(sock, (char*)buf, sizeof(buf));
						type  = (DDUIEventType)ntohl(buf[0]);
						owner = (DDUIEventOwner)ntohl(buf[1]);
						x     = ntohl(buf[2]);
						y     = ntohl(buf[3]);
						self->m_command_delegator->DDServerPortCommandUIEvent(type, owner, x, y);
					}
					break;
				case DD_SEND_KEYINPUTS:
					{
						DWORD len;
						char buf[256];
						self->m_is_connected = RecvByte(sock, (char*)&len, sizeof(len));
						len = ntohl(len);
						self->m_is_connected = RecvByte(sock, (char*)buf, len < sizeof(buf) ? len : sizeof(buf) - 1);
						buf[len] = '\0';
						self->m_command_delegator->DDServerPortCommandKeyinputs(buf, len);
					}
					break;
				case DD_SEND_DATA_PORT_NUMBER:
					{
						DWORD port;
						self->m_is_connected = RecvByte(sock, (char*)&port, sizeof(port));
						self->m_send_data_port = ntohl(port);
					}
					break;
				case DD_REQUEST_SCREEN_SIZE:
					{
						DWORD buf[2];
						buf[0] = ::htonl(::GetSystemMetrics(SM_CXSCREEN));
						buf[1] = ::htonl(::GetSystemMetrics(SM_CYSCREEN));
						self->m_is_connected = SendByte(sock, (const char*)buf, sizeof(buf));
					}
					break;
				case DD_START_DOWNSTREAM:
					self->OpenSendDataSocket();
					break;
				case DD_STOP_DOWNSTREAM:
					self->CloseSendDataSocket();
					break;
				case '\0':
					break;
				}
			}

			self->CloseSendDataSocket();
			closesocket(sock);
			self->m_command_delegator->DDServerPortChangeState();
		}

		return 0;
	}

	bool DDServerPort::OpenSendDataSocket(void)
	{
		if(m_send_data_socket != INVALID_SOCKET){
			CloseSendDataSocket();
		}

		m_send_data_socket = socket(AF_INET, SOCK_STREAM, 0);
		if(m_send_data_socket == INVALID_SOCKET){
			return false;
		}

		{
			int ret;
			int sock_buf_size = 0;
			ret = setsockopt(m_send_data_socket, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size) );
			if(ret != 0){
				return false;
			}
		}

		m_client_addr.sin_port = ntohs(m_send_data_port);
		if(connect(m_send_data_socket, (sockaddr*)&m_client_addr, sizeof(m_client_addr)) != 0){
			closesocket(m_send_data_socket);
			return false;
		}

		m_send_data_socket_is_opened = true;
		m_command_delegator->DDServerPortChangeState();

		return true;
	}

	bool DDServerPort::CloseSendDataSocket(void)
	{
		if(m_send_data_socket != INVALID_SOCKET){
			if(closesocket(m_send_data_socket) != 0){
				return false;
			}
			m_send_data_socket = INVALID_SOCKET;
		}
		m_send_data_socket_is_opened = false;
		m_command_delegator->DDServerPortChangeState();

		return true;
	}


	bool DDServerPort::SendDataSocketIsOpened(void) const
	{
		return m_send_data_socket_is_opened;
	}

	bool DDServerPort::IsConnected(void) const
	{
		return m_is_connected;
	}


	const char* DDServerPort::GetClientAddr(void) const
	{
		return m_client_addr_str;
	}


	int DDServerPort::GetDataPort(void) const
	{
		return m_send_data_port;
	}



	bool DDServerPort::SendData(const unsigned char* buf, int length, int x, int y, int width, int height, int zoom_scale)
	{
		const int PACKET_SIZE = 60000;
		int remain;
		int send_len;
		const char* send_pt;
		bool err;
		char header[1+4+4+4+4+4+4] = {DD_START_CODE};

		*(DWORD*)(&header[1+0]) = htonl(length);
		*(DWORD*)(&header[1+4]) = htonl(x);
		*(DWORD*)(&header[1+8]) = htonl(y);
		*(DWORD*)(&header[1+12]) = htonl(width);
		*(DWORD*)(&header[1+16]) = htonl(height);
		*(DWORD*)(&header[1+20]) = htonl(zoom_scale);

		err = false;
		err |= ::send(m_send_data_socket, header, sizeof(header), 0) <= 0;

		remain = length;
		send_pt = (const char*)buf;
		while(remain > 0 && err == false){
			send_len = ::send(m_send_data_socket, send_pt, remain >= PACKET_SIZE ? PACKET_SIZE : remain, 0);
			if(send_len <= 0){
				err = true;
			}
			remain -= send_len;
			send_pt += send_len;
		}

		return !err;
	}
}
